import os
import sys
import json
import re
from typing import Dict, List, Any, Optional, Tuple
from .code_block import CodeBlock, LineCodeBlock

class NapiGenCode:
    def __init__(self, module_name: str, nm_module: str, data: Dict[str, Any], userData:str = "userData"):
        self.export_result = data
        self.module_name = module_name
        self.nm_module = nm_module
        self.userData = userData
        self.result = None

    def format_string(self, content: str, indent: int = 1, enter: bool = True):
        format = ' ' * indent*4 + f"{content}"
        if enter:
            format += "\n"
        return format
    
    def is_object_type(self, t: str):
        for inf in self.export_result["interfaces"]:
            if t == inf['name']:
                return True
        return False
    
    def is_alias_type(self, t: str):
        for inf in self.export_result["type_aliases"]:
            if t == inf['name']:
                return True
        return False
    
    def to_cpp_type(self, t: str):
        if t == "number":
            return "int32_t"
        elif t == "string":
            return "std::string"
        elif t == "void":
            return "void"
        elif t == "boolean":
            return "bool"
        else:
            for inf in self.export_result["type_aliases"]:
                if t == inf['name']:
                    return "napi_ref"
            for inf in self.export_result["interfaces"]:
                if t == inf['name']:
                    return t
            return None
        
    def to_c_type(self, t: str):
        if t == "string":
            return "char *"
        else:
            return self.to_cpp_type(t)
        
    def to_napi_type(self, t: str):
        return ""
    
    def to_cpp_param(self, t: str, v: str):
        if t == "std::string":
            return v
        else:
            return v
        
    def to_c_param(self, t: str, v: str):
        if t == "std::string":
            return f"(char *){v}.c_str()"
        else:
            return v
        
    def to_params_list_c(self, parameters:list):
        params = []
        for index, p in enumerate(parameters):
            cpp_type = self.to_cpp_type(p['type'])
            params.append(f"{self.to_c_param(cpp_type, p['name'])}")
        return ", ".join(params)
    
    def to_params_list_cpp(self, parameters:list):
        params = []
        for index, p in enumerate(parameters):
            cpp_type = self.to_cpp_type(p['type'])
            params.append(f"{self.to_cpp_param(cpp_type, p['name'])}")
        return ", ".join(params)
    
    def to_params_type_list(self, parameters:list):
        params = []
        for index, p in enumerate(parameters):
            cpp_type = self.to_cpp_type(p['type'])
            params.append(f"{cpp_type}")
        return ", ".join(params)
    
    def to_params_define_list(self, parameters:list):
        params = []
        for index, p in enumerate(parameters):
            cpp_type = self.to_c_type(p['type'])
            params.append(f"{cpp_type} {p['name']}")
        return ", ".join(params)
    
    def cpp_to_napi_type(self, js_typ: str, value: str, handler: str = "napiHandler.") -> CodeBlock:
        cpp_type = self.to_cpp_type(js_typ)
        if cpp_type == None:
            return LineCodeBlock(f"error type in {js_typ}")
        if self.is_object_type(js_typ):
            objectBlock = CodeBlock(start=f"{handler}GetNapiValue<{cpp_type}>({value}, [&](const {cpp_type} &value) -> napi_value {{", end="})")
            objectBlock.add_line(f"return Serialize{cpp_type}({handler}.GetEnv(), value);")
            return objectBlock
        elif cpp_type == "void":
            return LineCodeBlock(f"{handler}GetVoidValue()")
        elif cpp_type == "napi_ref":
            objectBlock = CodeBlock(start=f"{handler}GetNapiValue<{cpp_type}>({value}, [&](const {cpp_type} &value) -> napi_value {{", end="})")
            objectBlock.add_line(f"return Serialize{js_typ}({handler}.GetEnv(), value);")
            return objectBlock
        else:
            return LineCodeBlock(f"{handler}GetNapiValue<{cpp_type}>({value})")
        
    def cpp_to_napi_objet_type(self, js_typ: str, name: str, obj: str, value: str, handler: str = "napiHandler"):
        cpp_type = self.to_cpp_type(js_typ)
        if cpp_type == None:
            return LineCodeBlock(f"error type in {js_typ}")
        if self.is_object_type(js_typ):
            objectBlock = CodeBlock(start=f"{handler}.SetObjectProp<{cpp_type}>({obj}, \"{name}\", {value}, [&](const {cpp_type} &value) -> napi_value {{", end="})")
            objectBlock.add_line(f"return Serialize{cpp_type}({handler}.GetEnv(), value);")
            return objectBlock
        elif cpp_type == "void":
            return LineCodeBlock(f"{handler}.GetVoidValue()")
        elif cpp_type == "napi_ref":
            objectBlock = CodeBlock(start=f"{handler}.SetObjectProp<{js_typ}>({obj}, \"{name}\", {value}, [&](const {js_typ} &value) -> napi_value {{", end="})")
            objectBlock.add_line(f"return Serialize{js_typ}({handler}.GetEnv(), value);")
            return objectBlock
        else:
            return LineCodeBlock(f"{handler}.SetObjectProp<{cpp_type}>({obj}, \"{name}\", {value})")
        
    def napi_to_cpp_type(self, js_typ: str, name: str, value: str, handler: str = "napiHandler"):
        cpp_type = self.to_cpp_type(js_typ)
        if cpp_type == None:
            print(f"find undefined type: {js_typ} in {name} in napi_to_cpp_type")
            return LineCodeBlock(f"{js_typ} {name} = error")
        if self.is_object_type(js_typ):
            objectBlock = CodeBlock(start=f"{cpp_type} {name} = {handler}.ParseArgAs<{cpp_type}>({value}, [&](const napi_value &obj) -> {cpp_type} {{", end="})")
            objectBlock.add_line(f"return Parse{cpp_type}(env, obj);")
            return objectBlock
        elif cpp_type == "napi_ref":
            objectBlock = CodeBlock(start=f"{js_typ} {name} = {handler}.ParseArgAs<{js_typ}>({value}, [&](const napi_value &obj) -> {js_typ} {{", end="})")
            objectBlock.add_line(f"return Parse{js_typ}(env, obj);")
            return objectBlock
        elif cpp_type == "void":
            return LineCodeBlock("")         
        else:
            return LineCodeBlock(f"{cpp_type} {name} = {handler}.ParseArgAs<{cpp_type}>({value})")
    
    def napi_to_cpp_object_type(self, js_typ: str, name: str, value: str, handler: str = "napiHandler") -> CodeBlock:
        napi_func = ""
        cpp_type = self.to_cpp_type(js_typ)
        if cpp_type == None:
            print(f"find undefined type: {js_typ} in {name}")
            return None
        if self.is_object_type(js_typ):
            napiBlock = CodeBlock(start=f"{handler}->GetObjectProp<{cpp_type}>({value}, \"{name}\", [&](const napi_value &obj) -> {cpp_type} {{", end="})")
            napiBlock.add_line(f"return Parse{cpp_type}(env, obj);")
            return napiBlock
        elif cpp_type == "napi_ref":
            # refBlock.add_block(self.napi_to_cpp_object_type(js_type, name, "obj"), prefix=f"napi_ref {name}Ref = ", postfix=";")
            #         parserBlock.add_line(f"napiHandler->BindMethod(\"{name}\", {name}Ref);")
            #         parserBlock.add_block(self.get_alias_function(js_type, name, 1), prefix=f"result.{name} = ", postfix=";")
            napiBlock = CodeBlock(start=f"{handler}->GetObjectProp<{js_typ}>({value}, \"{name}\", [&](const napi_value &obj) -> {js_typ} {{", end="});")
            napiBlock.add_line(f"napi_ref ref = Parse{js_typ}(env, obj);")
            napiBlock.add_line(f"{handler}->BindMethod(\"{name}\", ref);")
            napiBlock.add_block(self.get_alias_function(js_typ, name, 1), prefix=f"return ", postfix=";")
            return napiBlock
        elif cpp_type == "void":
            return None         
        else:
            napiBlock = LineCodeBlock(start=f"{handler}->GetObjectProp<{cpp_type}>({value}, \"{name}\")")
            return napiBlock
        return None
        
    def ref_to_functiontype(self, js_return_type: str, parameters: list):
        return f"std::function<{self.to_cpp_type(js_return_type)}({self.to_params_type_list(parameters)})>"
        
    def export_alias_parser(self, name, parameters, return_type) -> CodeBlock:
        return_cpp_type = self.ref_to_functiontype(return_type, parameters)
        parserBlock = CodeBlock(start=f"static napi_ref Parse{name}(napi_env env, const napi_value &obj)\n{{", end = "}")
        parserBlock.add_line("NapiHandler napiHandler(env);")
        parserBlock.add_line(f"return napiHandler.ParseArg<napi_ref>(obj);")
        return parserBlock
    
    def get_alias_function(self, name: str, method: str, indent: int):
        alias_type = None
        for inf in self.export_result["type_aliases"]:
            if name == inf['name']:
                alias_type = inf
        if alias_type is None:
            print(f"no alias defined: {name}")
            return ""
        parameters = alias_type['parameters']
        return_type = alias_type['return_type']
        return_cpp_type = self.to_cpp_type(return_type)
        param_list = self.to_params_define_list(parameters)
        parserBlock = CodeBlock(start=f"[](void *userData{", " if len(param_list) > 0 else ""}{param_list}) -> {return_cpp_type} {{", end="}")
        parserBlock.add_line(f"NAPI_HANDLER_CHECK_GET(napiHandler, userData);")
        argsBlock = CodeBlock(start= "std::vector<napi_value> params = {", end="};", sep=",\n")

        for index, p in enumerate(parameters):
            argsBlock.add_block(self.cpp_to_napi_type(p['type'], p['name'], "napiHandler->"))
        parserBlock.add_block(argsBlock)
        parserBlock.add_line(f"NAPI_METHOD_CALL(napiHandler, \"{method}\", params);")
        return parserBlock

    def export_alias_getter(self, name, parameters, return_type):
        cpp_type = self.ref_to_functiontype(return_type, parameters)
        getterBlock = CodeBlock(start=f"static napi_value Serialize{name}(napi_env env, const {name} &value)\n{{", end="}")
        getterBlock.add_line(f"return nullptr;")
        return getterBlock
    
    # 将napi 类型转换为cpp类型
    def export_interface_parser(self, name: str, members: list) -> CodeBlock:
        return_cpp_type = name

        parserBlock = CodeBlock(start=f"static {return_cpp_type} Parse{name}(napi_env env, const napi_value &obj)\n{{", end="}")
        parserBlock.add_line("NapiHandler *napiHandler = new NapiHandler(env);")
        parserBlock.add_line(f"{return_cpp_type} result;")
        parserBlock.add_line(f"USER_DATA(result, napiHandler);")

        for index, p in enumerate(members):
            mem_type = p['type']
            js_type = p['data_type']
            name = p['name']
            optional = p['optional'],
            if mem_type == "property":
                # if self.is_alias_type(js_type):
                #     parserBlock.add_block(self.napi_to_cpp_object_type(js_type, name, "obj"), prefix=f"napi_ref {name}Ref = ", postfix=";")
                #     parserBlock.add_line(f"napiHandler->BindMethod(\"{name}\", {name}Ref);")
                #     parserBlock.add_block(self.get_alias_function(js_type, name, 1), prefix=f"result.{name} = ", postfix=";")
                # else:
                parserBlock.add_block(self.napi_to_cpp_object_type(js_type, name, "obj"), f"result.{name} = ", postfix=";")
            else:
                print(f"{mem_type} not impliment in {p}")

        parserBlock.add_line(f"return result;")
        return parserBlock

    def export_interface_getter(self, name: str, members: list) -> CodeBlock:
        return_cpp_type = name
        getterBlock = CodeBlock(start=f"static napi_value Serialize{name}(napi_env env, const {return_cpp_type} &data)\n{{", end="}")
        getterBlock.add_line(f"NapiHandler napiHandler(env);")
        getterBlock.add_line(f"napi_value result = napiHandler.CreateObject();")
        for index, p in enumerate(members):
            mem_type = p['type']
            js_type = p['data_type']
            name = p['name']
            optional = p['optional']
            if mem_type == "property":
                value = f"data.{name}"
                getterBlock.add_block(self.cpp_to_napi_objet_type(js_type, name, 'result', value), postfix=";")
            else:
                print(f"{mem_type} not impliment in {p}")
        getterBlock.add_line(f"return result;")
        return getterBlock
    
    def export_napi_function(self, funcName: str, parameters: list, return_js_type: str) -> CodeBlock:
        funcBlock = CodeBlock(start=f"static napi_value {funcName}(napi_env env, napi_callback_info info)\n{{", end="}", parent=None)
        funcBlock.add_line(f"NapiHandler napiHandler(env, info, PARAM_COUNT_{len(parameters)});")
        funcBlock.add_blank_line()
        funcBlock.add_comment("解析参数列表")

        params = self.to_params_list_c(parameters)
        for index, p in enumerate(parameters):
            js_type = p['type']
            cpp_type = self.to_cpp_type(js_type)
                
            if cpp_type == None:
                print(f"find undefined type: {p['type']} in {func} export_function_defined")
                continue
            if self.is_object_type(js_type):
                parseBlock = CodeBlock(start=f"{cpp_type} {p['name']} = napiHandler.ParseArgAs<{cpp_type}>(INDEX_{index}, [&](const napi_value &obj) -> {cpp_type} {{", end="});")
                parseBlock.add_line(f"return Parse{cpp_type}(env, obj);")
                funcBlock.add_block(parseBlock)
            elif cpp_type == "napi_ref":
                parseBlock = CodeBlock(start=f"{js_type} {p['name']} = napiHandler.ParseArgAs<{js_type}>(INDEX_{index}, [&](const napi_value &obj) -> {js_type} {{", end="});")
                parseBlock.add_line(f"return Parse{js_type}(env, obj);")
                funcBlock.add_block(parseBlock)             
            else:
                funcBlock.add_line(f"{cpp_type} {p['name']} = napiHandler.ParseArgAs<{cpp_type}>(INDEX_{index});")

        funcBlock.add_blank_line()
        funcBlock.add_comment("功能实现")
        return_type = self.to_cpp_type(return_js_type)
        if return_type == "void":
            funcBlock.add_line(f"{funcName}({params});")
        else:
            funcBlock.add_line(f"{return_type} result = {funcName}({params});")

        if return_type == None:
            print(f"find undefined return_type: {return_type} in {funcName}")
            funcBlock.add_line("return napiHandler.GetVoidValue();")
        elif self.is_object_type(return_js_type):
            funcBlock.add_line(f"return Serialize{return_js_type}(env, result);")
        elif return_type == "void":
            funcBlock.add_line("return napiHandler.GetVoidValue();")
        else:
            funcBlock.add_line(f"return napiHandler.GetNapiValue<{return_type}>(result);")
        return funcBlock

    def export_function_defined(self, moduleName):
        rootBlock = CodeBlock(start=f"namespace  {moduleName} {{", end="}\n\n")
        rootBlock.set_forbit_indent(True)

        for func in self.export_result['type_aliases']:
            name = func['name']
            return_type = func['return_type']
            parameters = func['parameters']
            rootBlock.add_block(self.export_alias_parser(name, parameters, return_type))
            rootBlock.add_blank_line()
            rootBlock.add_block(self.export_alias_getter(name, parameters, return_type))
            rootBlock.add_blank_line()

        for func in self.export_result['interfaces']:
            name = func['name']
            members = func['members']
            rootBlock.add_block(self.export_interface_parser(name, members))
            rootBlock.add_block(self.export_interface_getter(name, members))
            rootBlock.add_blank_line()

        for func in self.export_result['exported_functions']:
            name = func['name']
            parameters = func['parameters']
            return_js_type = func['return_type']
            funcBlock  = self.export_napi_function(name, parameters, return_js_type)
            rootBlock.add_block(funcBlock)
            rootBlock.add_blank_line()
        
        return rootBlock
    
    def export_napi_init(self, nativeName: str):
        rootBlock = CodeBlock(start="EXTERN_C_START", end= "EXTERN_C_END")
        rootBlock.set_forbit_indent(True)
        funcBlock = CodeBlock(start="static napi_value Init(napi_env env, napi_value exports) \n{", end= "}", parent= rootBlock)
        descBlock = CodeBlock(start="napi_property_descriptor desc[] = {", end= "};", sep = ",\n")
        
        for func in self.export_result['exported_functions']:
            funcName = func['name']
            descBlock.add_line(f"DECLARE_NAPI_FUNCTION(\"{funcName}\", {self.module_name}::{funcName})")
        funcBlock.add_block(descBlock)
        funcBlock.add_lines([
            "napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);",
            "return exports;"
        ])
        moduleBlock = CodeBlock(start= "static napi_module demoModule = {", end= "};", sep= ",\n")
        moduleBlock.add_lines([
            ".nm_version = 1",
            ".nm_flags = 0",
            ".nm_filename = nullptr",
            ".nm_register_func = Init",
            f".nm_modname = \"{nativeName}\"",
            ".nm_priv = ((void *)0)"
        ])
        rootBlock.add_block(moduleBlock)
        rootBlock.add_lines([
            "extern \"C\" __attribute__((constructor)) void RegisterModule(void) {",
            "    napi_module_register(&demoModule);",
            "}"])
        return rootBlock
    
    def get_param_random_any(self, js_type: str, name: str):
        if self.is_object_type(js_type):
            return self.get_params_random_object(js_type)
        elif js_type == "number":
            return "1"
        elif js_type == "string":
            return f"\"{name}\""
        elif js_type == "boolean":
            return "true"
        elif self.is_alias_type(js_type):
            return self.get_params_random_alias(js_type)
        else:
            return "null"
    
    def get_params_random_object(self, interface: str) -> CodeBlock:
        for inf in self.export_result['interfaces']:
            if inf['name'] == interface:
                objectBlock = CodeBlock(sep=",\n")
                members = inf['members']
                for index, p in enumerate(members):
                    name = p['name']
                    data_type = p['data_type']
                    last = (index == len(members)-1)
                    objectBlock.add_block(self.get_param_random_any(data_type, name), f"{name}: ")
                return objectBlock
        return None
    
    def get_params_random_alias(self, interface: str) -> CodeBlock:
        for inf in self.export_result['type_aliases']:
            if inf['name'] == interface:
                
                interface_value = []
                console = []
                parameters = inf['parameters']
                return_type = inf['return_type']
                for index, p in enumerate(parameters):
                    name = p['name']
                    data_type = p['type']
                    last = (index == len(parameters)-1)
                    console.append(f"+ \"{name}: \" + {name}")
                    interface_value.append(f"{name}: {data_type}")
                codeBlock = CodeBlock(start= f"({', '.join(interface_value)}) => {{", end="}")
                codeBlock.add_line(f"console.debug(\"{interface}: \" {"+ \",\" ".join(console)});")
                return codeBlock
        return CodeBlock(start = "() => {", end="}")

    
    def get_params_random_value(self, params: list) -> list:
        param_list = []
        for index, p in enumerate(params):
            js_type = p['type']
            name = p['name']
            param_list.append(self.get_param_random_any(js_type, name))
        return param_list
    
    def export_test(self) -> CodeBlock:
        rootBlock = CodeBlock(start="export default function abilityTest() {", end="}",ic=2)
        describeBlock = CodeBlock(start="describe('ActsAbilityTest', () => {", end="})", parent= rootBlock)
        for func in self.export_result['exported_functions']:
            name = func['name']
            params = func['parameters']
            testSuitBlock = CodeBlock(start=f"it('{name}', 0, () => {{", end="});\n", parent=describeBlock)
            funcBlock = CodeBlock(start=f"{self.module_name}.{name}(", end=");", parent=testSuitBlock, sep=",\n")
            for block in self.get_params_random_value(params):
                funcBlock += block
        return rootBlock

    def export_napi(self) -> list[CodeBlock]:
        blocks = []
        blocks.append(self.export_function_defined(self.module_name))
        blocks.append(self.export_napi_init(self.nm_module))
        return blocks

    
        